#region Copyright (c) 2003-2004 ComponentGo!
/*
*********************************************************************
*                                                                   *
*       ComponentGo! Component Library                              *
*                                                                   *
*       Copyright (c) 2003-2004 ComponentGo!                        *
*       ALL RIGHTS RESERVED                                         *
*                                                                   *
*********************************************************************
*/
#endregion

using System;
using System.Runtime.Serialization;
using System.Diagnostics;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Data;
using System.Windows.Forms;

namespace ComponentGo.Calendars {

#if !DAILY

	/// <summary>
	/// A control displaying appointments in a form of a horizontal schedule.
	/// </summary>
	/// <remarks>
	/// A control allowing for convenien management of many resources.
	/// There is a separate row for each resource.`
	/// </remarks>
	[Designer(typeof(Design.BaseCalendarDesigner), typeof(System.ComponentModel.Design.IDesigner))]
	[System.Runtime.InteropServices.ComVisible(true)]
	public class ResourceScheduler : BaseCalendar {
		
		#region Types

		/// <summary>
		/// Contains the values indentifying various regions of the control.
		/// </summary>
		/// <remarks>
		/// Used by various methods to determine a region (rectangle), the control 
		/// is inquired about
		/// <seealso cref="GetRegionRectangle"/>
		/// </remarks>
		public enum Regions { 
			/// <summary>
			/// Region containing information on dates/times of appointments displayed.
			/// Region is located in upper part of a control and has a shape of a ruler.
			/// </summary>
			DatesLabel, 

			/// <summary>
			/// Panel on the left side of a control containing labels of resources 
			/// displayed in a control.
			/// </summary>
			ResourcesLabel, 

			/// <summary>
			/// Working area containing appoinments' rectangles 
			/// laid out with hourly resolution.
			/// </summary>
			Workspace, 
		}

		#endregion

		#region General

		/// <summary>
		/// Constructor creating a time plan control. 
		/// Properties are initialized to standard values.
		/// </summary>
		public ResourceScheduler() : this(null) {
			Size = new Size(250, 120);
		}

		internal ResourceScheduler(RootCalendar root) : base(root, true, true) {
			Size = new Size(250, 120);
		}

		#endregion

		#region Overrides

		static readonly DateTime ScrollZero = new DateTime(2000, 1, 1);

		internal override void initScrollBars() {
			if (Width==0) return;

			////////////////////////////////////////////////////////////////
			// Horizontal ScrollBar
			// 
			int v = (int)(FirstDateTime-ScrollZero).TotalSeconds;
			int cellMin = (int)minsResolution.TotalSeconds;
			int pageMin = Math.Max((cellItemCount-1)*cellMin, 0);
			int vr = VisibleResources;
			bool enable = pageMin>0 && vr-1>0;
			int widthWin = pageMin*15;

			hScroll.Minimum = v-widthWin;
			hScroll.Maximum = v+widthWin+pageMin;
			hScroll.Value = v;
			hScroll.LargeChange = pageMin;
			hScroll.SmallChange = cellMin;
			hScroll.Enabled = enable;

			///////////////////////////////////////////////////////////////
			// Vertical ScrollBar
			// 
			vScroll.Minimum = 0;
			vScroll.Maximum = Resources.Count;
			vScroll.Value = FirstResource;
			vScroll.LargeChange = vr;
			vScroll.SmallChange = 1;
			vScroll.Enabled = vr-1<Resources.Count && Resources.Count>1 && enable;
		}

		internal override void hScroll_Scroll(object sender, ScrollEventArgs e) {
			if (e.Type!=ScrollEventType.EndScroll)
				setFirstDateTime(ScrollZero.AddSeconds(e.NewValue), false);
			else {
				int v = hScroll.Value-hScroll.Minimum;
				int w = hScroll.Maximum-hScroll.Minimum;
				bool change = false;
				if (v<w/10) change = true;
				if (v+hScroll.LargeChange>w-w/10) change = true;
				if (change)
					initScrollBars();
			}
		}

		internal override void vScroll_Scroll(object sender, ScrollEventArgs e) {
			setFirstResource(e.NewValue, false);
		}

		/// <summary>
		///	Raises the <see cref="BaseCalendar.HoursResolutionChanged"/> event.
		/// </summary>
		/// <remarks>
		/// Contains implementation specific to a ResourceScheduler control.
		/// </remarks>
		/// <param name="args">An <see cref="EventArgs"/> that contains the event data.</param>
		protected override void OnHoursResolutionChanged(EventArgs args) {
			base.OnHoursResolutionChanged(args);
			setFirstDateTime(FirstDateTime, true);
		}

		/// <summary>
		/// Forces visibility of a given period in a calendar.
		/// </summary>
		/// <param name="dateFrom">A beginning of a period, that is to be visible in a calendar.</param>
		/// <param name="dateTo">An end of a period, that is to be visible in a calendar.</param>
		/// <param name="resourceIdx">A index of resource, that is to be visible in a calendar.</param>
		/// <remarks>
		/// It is possible that a given period wil not be wholly visible, because a calendar
		/// is too small. In this case a largest possible portion of a period will be visible.
		/// </remarks>
		public override void EnsureVisible(DateTime dateFrom, DateTime dateTo, int resourceIdx) {
			if (dateFrom<FirstDateTime)
				FirstDateTime = dateFrom;
			TimeSpan ts = VisibleTime;
			if (dateTo>=FirstDateTime+ts)
				FirstDateTime = dateTo-ts;
			if (FirstResource>resourceIdx)
				FirstResource = resourceIdx;
			else {
				int vc = VisibleResources-2;
				int r = resourceIdx-vc;
				if (FirstResource<=r && r<Resources.Count && r>=0)
					FirstResource = r;
			}
		}

		#endregion

		#region Appointments

		ArrayList currentApps = null;

		internal override RequestAppointmentsEventArgs CreateRequestAppointmentsArgs() {
			if(!printing)
			{
				return new RequestAppointmentsEventArgs(
					FirstDateTime, FirstDateTime+VisibleTime,
					FirstResource, Math.Min(FirstResource+VisibleResources, Resources.Count));
			}
			else
			{
				return new RequestAppointmentsEventArgs(
					PrintDate.Add(StartPrintTime), PrintDate.Add(FinishPrintTime),
					0, Resources.Count);
			}
		}

		public override void Resync(bool recalc) {
			if (recalc)
			{
				currentApps = null;
				initScrollBars();
			}
			Invalidate();
		}

		ArrayList CurrentApps {
			get {
				if (currentApps==null) {
					RequestAppointmentsEventArgs args = forceAppointments();
					ArrayList arr = new ArrayList();
					foreach (Appointment ap in Appointments)
						if (args.Check(ap))
							arr.Add(ap);
					currentApps = arr;
				}
				return currentApps;
			}
		}

		#endregion

		#region FirstDateTime

		DateTime firstDateTime = DateTime.Today;

		/// <summary>
		/// An date and hour displayed as the first (first from the left).
		/// </summary>
		/// <remarks>
		/// The number of displayed days/hours depends on its width and
		/// <see cref="BaseCalendar.HoursResolution"/> property.
		/// </remarks>
		[Category("Data")]
		[Description("An hour displayed as the first (first from the left).")]
		public DateTime FirstDateTime {
			get { return firstDateTime; }
			set { setFirstDateTime(value, true); }
		}

		void setFirstDateTime(DateTime value, bool withScollBars) {
			value = round(value, false);
			if (firstDateTime==value) return;
			if (!stopEdit(false)) return;
			firstDateTime = value;
			Resync(true);
			OnFirstDateTimeChanged(EventArgs.Empty);
			if (withScollBars)
				initScrollBars();
		}

		/// <summary>
		/// Indicates whether the <see cref="FirstDateTime"/> property should be persisted.
		/// </summary>
		/// <returns><b>true</b> if the property value has changed from its 
		/// default; otherwise, <b>false</b>.</returns>
		/// <remarks>You typically use this method if you are creating a designer
		/// for <see cref="ResourceScheduler"/>.
		/// </remarks>
		public bool ShouldSerializeFirstDateTime() {
			return FirstDateTime!=DateTime.Today;
		}

		/// <summary>
		/// Resets a <see cref="FirstDateTime"/> property to a default value.
		/// </summary>
		public void ResetFirstDateTime() {
			FirstDateTime = DateTime.Today;
		}

		/// <summary>
		/// An event raized after the <see cref="FirstDateTime"/> property change.
		/// </summary>
		[Category("Property Changed")]
		[Description("An event raized after the FirstDateTime property change")]
		public event EventHandler FirstDateTimeChanged {
			add { FirstDateChanged += value; }
			remove { FirstDateChanged -= value; }
		}

		/// <summary>
		///	Raises the <see cref="FirstDateTimeChanged"/> event.
		/// </summary>
		/// <param name="args">An <see cref="EventArgs"/> that contains the event data.</param>
		protected virtual void OnFirstDateTimeChanged(EventArgs args) {
			OnFirstDateChanged(args);
		}

		internal override DateTime VisibleFirstDateTime {
			get { return FirstDateTime; }
			set { FirstDateTime = value; }
		}

		internal override TimeSpan VisibleTimeSpan {
			get { return VisibleTime; }
			set { throw new NotSupportedException(); }
		}

		#endregion

		#region FirstResource

		int firstResource = 0;

		/// <summary>
		/// Index of a first resource displayed in the upper part of a ResourceScheduler control.
		/// </summary>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Raised, when an index of a resource is less than 0 or if it is beyond the collection <see cref="ResourceCollection"/>.
		/// </exception>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public int FirstResource {
			get { return firstResource; }
			set { setFirstResource(value, true); }
		}

		void setFirstResource(int value, bool withScrollBars) {
			if (!(0<=value && value<Resources.Count))
				throw new ArgumentOutOfRangeException("value", value, "Argument out of range");
			if (firstResource==value) return;
			if (!stopEdit(false)) return;
			firstResource = value;
			Resync(true);
			if (withScrollBars)
				initScrollBars();
			OnFirstResourceChanged(EventArgs.Empty);
		}

		/// <summary>
		/// An event raized after the <see cref="FirstResource"/> property change.
		/// </summary>
		[Category("Property Changed")]
		[Description("An event raized after the FirstResource property change")]
		public event EventHandler FirstResourceChanged;

		/// <summary>
		///	Raises the <see cref="FirstResourceChanged"/> event.
		/// </summary>
		/// <param name="args">An <see cref="EventArgs"/> that contains the event data.</param>
		protected virtual void OnFirstResourceChanged(EventArgs args) {
			if (FirstResourceChanged!=null)
				FirstResourceChanged(this, args);
		}

		#endregion

		#region Appearance

		int cellWidth = 15;

		/// <summary>
		/// Returns a width of a cell containing a single period.
		/// </summary>
		/// <remarks>
		/// Duration of a single period depends on <see cref="BaseCalendar.HoursResolution"/>
		/// </remarks>
		[DefaultValue(15)]
		[Category("Appearance")]
		[Description("Returns a width of a cell containing a single period.")]
		public int CellWidth {
			get {
				return cellWidth;
			}
			set {
				if (cellWidth==value) return;
				cellWidth = value;
				Resync(true);
			}
		}
		
		int resourceWidth = 100;

		/// <summary>
		/// Determines width of a panel containing appointments' resource information.
		/// </summary>
		/// <remarks>
		/// Resource panel is located on the left of a control.
		/// </remarks>
		[DefaultValue(100)]
		[Category("Appearance")]
		[Description("Determines width of a panel containing appointments' resource information.")]
		public int ResourceWidth {
			get {
				return resourceWidth;
			}
			set {
				if (resourceWidth==value) return;
				resourceWidth = value;
				Resync(true);
			}
		}

		int rowHeight = 0;

		/// <summary>
		/// Determines height of a row containing appointments' resource information.
		/// </summary>
		/// <remarks>
		/// SA default value of 0 means that a row height depends on the font selected. 
		/// </remarks>
		[DefaultValue(0)]
		[Category("Appearance")]
		[Description("Determines height of a row containing appointments' resource information.")]
		public int RowHeight {
			get {
				return rowHeight;
			}
			set {
				if (rowHeight==value) return;
				rowHeight = value;
				Resync(true);
			}
		}

		string dateFormat = null;

		/// <summary>
		/// Format a date displayed in a control's header.
		/// </summary>
		/// <remarks>
		/// Default data format depend on resolution <see cref="BaseCalendar.HoursResolution"/> of a time axis. 
		/// For values of <see cref="HoursResolutions.OneDay"/> and <see cref="HoursResolutions.Twelve"/> 
		/// format "<b>y</b>" is used, for other resolutions "<b>D</b>".
		/// </remarks>
		[DefaultValue(null)]
		[Category("Appearance")]
		[Description("Format a date displayed in a control's header.")]
		public string DateFormat {
			get {
				return dateFormat;
			}
			set {
				if (value=="") value = null;
				if (value==dateFormat) return;
				dateFormat = value;
				Invalidate();
			}
		}

		Font dateFont = null;

		/// <summary> 
		/// Font used to display a date in an upper part of a control. 
		/// </summary> 
		/// <remarks> 
		/// A default value of <b>null</b>b> means that a default (Arial 11pt) font will be used. 
		/// </remarks> 
		[DefaultValue(null)]
		[Category("Appearance")]
		[Description("Font used to display a date in an upper part of a control.")]
		public Font DateFont {
			get {
				return dateFont;
			}
			set {
				dateFont = value;
				Invalidate();
			}
		}

		ContentAlignment textAlign = ContentAlignment.MiddleLeft;

		/// <summary>
		/// Gets or sets the alignment of text in the appointment box.
		/// </summary>
		/// <value>
		/// One of the <see cref="ContentAlignment"/> values. The default is 
		/// <see cref="ContentAlignment.MiddleLeft"/>.
		/// </value>
		[DefaultValue(ContentAlignment.MiddleLeft)]
		[Category("Appearance")]
		[Description("Gets or sets the alignment of text in the appointment box.")]
		public ContentAlignment TextAlign {
			get {
				return textAlign;
			}
			set {
				if (textAlign==value) return;
				textAlign = value;
				Invalidate();
			}
		}

		ContentAlignment resourceAlign = ContentAlignment.MiddleLeft;

		/// <summary>
		/// Gets or sets the alignment of text in the resource box.
		/// </summary>
		/// <value>
		/// One of the <see cref="ContentAlignment"/> values. The default is 
		/// <see cref="ContentAlignment.MiddleLeft"/>.
		/// </value>
		[DefaultValue(ContentAlignment.MiddleLeft)]
		[Category("Appearance")]
		[Description("Gets or sets the alignment of text in the resource box.")]
		public ContentAlignment ResourceAlign {
			get {
				return resourceAlign;
			}
			set {
				if (resourceAlign==value) return;
				resourceAlign = value;
				Invalidate();
			}
		}

		#endregion

		#region Colors properties

		#region DateRuler

		Color datesLabelColor = SystemColors.Control;
		Color datesLabelColorSecond = SystemColors.Control;

		/// <summary>
		/// A color of an date bar.
		/// </summary>
		/// <remarks>
		/// An date bar is located in a top portion of a control.
		/// </remarks>
		[Category("Colors")]
		[Description("A color of an hour bar")]
		public Color DatesLabelColor {
			get { return datesLabelColor; }
			set {
				if (datesLabelColor==value) return;
				if (datesLabelColor==datesLabelColorSecond)
					datesLabelColorSecond = value;
				datesLabelColor = value;
				Invalidate();
			}
		}

		/// <summary>
		/// Indicates whether the <see cref="DatesLabelColor"/> property should be persisted.
		/// </summary>
		/// <returns><b>true</b> if the property value has changed from its 
		/// default; otherwise, <b>false</b>.</returns>
		/// <remarks>You typically use this method if you are creating a designer
		/// for <see cref="ResourceScheduler"/>.
		/// </remarks>
		public bool ShouldSerializeDatesLabelColor() {
			return datesLabelColor!=SystemColors.Control;
		}

		/// <summary>
		/// Second color for a <see cref="DatesLabelColor"/> property used 
		/// for a linear gradient
		/// </summary>
		[Category("Colors")]
		[Description("Second color for an hour bar used for a linear gradient")]
		public Color DatesLabelColorSecond {
			get { return datesLabelColorSecond; }
			set {
				if (datesLabelColorSecond==value) return;
				datesLabelColorSecond = value;
				Invalidate();
			}
		}

		/// <summary>
		/// Indicates whether the <see cref="DatesLabelColorSecond"/> property 
		/// should be persisted.
		/// </summary>
		/// <returns><b>true</b> if the property value has changed from its 
		/// default; otherwise, <b>false</b>.</returns>
		/// <remarks>You typically use this method if you are creating a designer
		/// for <see cref="ResourceScheduler"/>.
		/// </remarks>
		public bool ShouldSerializeDatesLabelColorSecond() {
			return datesLabelColor!=datesLabelColorSecond;
		}

		#endregion

		#region ResourceRuler

		Color resourcesLabelColor = SystemColors.Control;
		Color resourcesLabelColorSecond = SystemColors.Control;

		/// <summary>
		/// A color of an resource bar.
		/// </summary>
		/// <remarks>
		/// An resource bar is located in a left portion of a control.
		/// </remarks>
		[Category("Colors")]
		[Description("A color of an resource bar")]
		public Color ResourcesLabelColor {
			get { return resourcesLabelColor; }
			set {
				if (resourcesLabelColor==value) return;
				if (resourcesLabelColor==resourcesLabelColorSecond)
					resourcesLabelColorSecond = value;
				resourcesLabelColor = value;
				Invalidate();
			}
		}

		/// <summary>
		/// Indicates whether the <see cref="ResourcesLabelColor"/> property should be persisted.
		/// </summary>
		/// <returns><b>true</b> if the property value has changed from its 
		/// default; otherwise, <b>false</b>.</returns>
		/// <remarks>You typically use this method if you are creating a designer
		/// for <see cref="ResourceScheduler"/>.
		/// </remarks>
		public bool ShouldSerializeResourcesLabelColor() {
			return resourcesLabelColor!=SystemColors.Control;
		}

		/// <summary>
		/// Second color for a <see cref="ResourcesLabelColor"/> property used 
		/// for a linear gradient
		/// </summary>
		[Category("Colors")]
		[Description("Second color for an resources bar used for a linear gradient")]
		public Color ResourcesLabelColorSecond {
			get { return resourcesLabelColorSecond; }
			set {
				if (resourcesLabelColorSecond==value) return;
				resourcesLabelColorSecond = value;
				Invalidate();
			}
		}

		/// <summary>
		/// Indicates whether the <see cref="ResourcesLabelColorSecond"/> property 
		/// should be persisted.
		/// </summary>
		/// <returns><b>true</b> if the property value has changed from its 
		/// default; otherwise, <b>false</b>.</returns>
		/// <remarks>You typically use this method if you are creating a designer
		/// for <see cref="ResourceScheduler"/>.
		/// </remarks>
		public bool ShouldSerializeResourcesLabelColorSecond() {
			return resourcesLabelColor!=resourcesLabelColorSecond;
		}

		#endregion

		#endregion

		#region Rectangles

		int labelHeight {
			get {
				if (DateFont==null)
					return 32+17;
				else 
					return 32+DateFont.Height;
			}
		}

		const int markHeight = 7;

		/// <summary>
		/// Returns a rectangle for a region.
		/// </summary>
		/// <param name="region">Region, a method is to return a rectangle for.</param>
		/// <returns>Rectangle for a region. Coordinates relative to a control.</returns>
		public Rectangle GetRegionRectangle(Regions region) {
			switch (region) {
				case Regions.DatesLabel:
					return new Rectangle(
						Point.Empty, 
						new Size(Width-VerticalScrollBarWidth, labelHeight));

				case Regions.ResourcesLabel:
					return new Rectangle(
						new Point(0, labelHeight),
						new Size(ResourceWidth, Height-HorizontalScrollBarHeight-labelHeight));

				case Regions.Workspace: {
					Size size = new Size(
						Math.Max(Width-ResourceWidth-VerticalScrollBarWidth, 0), 
						Math.Max(Height-labelHeight-HorizontalScrollBarHeight, 0));
					return new Rectangle(
						new Point(ResourceWidth, labelHeight), size);
				}

			}
			throw new ArgumentException("Unexpected region value", "region");
		}

		#region -- Custom Printing -- 


		#region + Printing Process -

		#region - Print - 
		/// <summary>
		/// Performs calendar printing.
		/// </summary>
		/// <param name="graphics"></param>
		public override void Print(Graphics graphics)
		{
			StartPrinting();
			try
			{
				PrintDatesLabel(graphics);
				PrintResourcesLabel(graphics);
				PrintWorkspace(graphics);
			}
			finally
			{
				FinishPrinting();
			}
		}
		#endregion

		#region - Start/Finish Printing -

		private bool printing = false;

		/// <summary>
		/// Prepare the control for printing.
		/// </summary>
		private void StartPrinting()
		{
			printing = true;
		}

		/// <summary>
		/// Finishing control printing.
		/// </summary>
		private void FinishPrinting()
		{
			printCanvasRectangle = Rectangle.Empty;
			printing = false;
		}
		#endregion

		#region - PrintDatesLabel -
		/// <summary>
		/// Prints the dates label.
		/// </summary>
		/// <param name="g">The g.</param>
		void PrintDatesLabel(Graphics g) 
		{
			Rectangle rt = GetPrintRegionRectangle(Regions.DatesLabel);
			//rt.Height -= markHeight;
			if (rt.Width<=0) return;

			using (Font font = new Font("Arial", 9))
			using (Font fontHead = new Font("Arial", 11))
			using (Font fontAMPM = new Font("Arial", 7))
			using (Brush textBrush = new SolidBrush(ForeColor))
			using (Brush markBrush = new SolidBrush(SystemColors.Window))
			using (Pen markPen = new Pen(LineColor, 1))
			using (Pen linePen = new Pen(ControlPaint.Dark(DatesLabelColorSecond), 1)) 
			{

				Font dateFont = DateFont==null ? fontHead : DateFont;

				CalendarTools.FillRectangle(g, rt, DatesLabelColor, DatesLabelColorSecond, true);
				bool daysRes = HoursResolution==HoursResolutions.OneDay || HoursResolution==HoursResolutions.Twelve;

				bool ampm = CalendarTools.AMPM && !daysRes;
				TimeSpan minRes = minsResolution;
				DateTime dt = PrintDate.Add(StartPrintTime);
				int x = ResourceWidth;
				int firstDate = int.MaxValue;
				bool first = true;
				int cellsCount = PrintCellItemCount;

				for (int i = 0; i<cellsCount; ++i) 
				{

					string lbl = null;
					bool boldLine = false;

					if (getDateInfo(dt, ampm, ref first, ref boldLine, ref lbl))
						g.DrawString(dt.Hour<12 ? "am" : "pm", 
							fontAMPM, textBrush, x+14, rt.Height-23);

					int lineHeight = 8;
					if (lbl!=null) 
					{
						g.DrawString(lbl, font, textBrush, x, rt.Height-22);
						lineHeight = 20;

						if (lbl!="" && (daysRes ? dt.Day==1 : dt.Hour==0 && dt.Minute==0 && dt.Second==0)) 
						{
							g.DrawLine(linePen, x, 5, x, rt.Height-5);
							g.DrawString(dateToString(daysRes, dt), dateFont, textBrush, x, 5);
							if (firstDate==int.MaxValue)
								firstDate = x;
						}
					}
					g.DrawLine(linePen, x, rt.Bottom-lineHeight, x, rt.Bottom);
					if (boldLine)
						g.DrawLine(linePen, x-1, rt.Bottom-lineHeight, x-1, rt.Bottom);

					dt += minRes;
					x += cellWidth;
				}

				if (firstDate>ResourceWidth) 
				{
					string s = dateToString(daysRes, FirstDateTime);
					float dateW = g.MeasureString(s, dateFont).Width;
					float xx = firstDate-dateW;
					if (xx>ResourceWidth) xx = ResourceWidth;
					g.DrawString(s, dateFont, textBrush, xx, 5);
				}
			}
		}
		#endregion

		#region - PrintResourcesLabel -
		/// <summary>
		/// Prints the resources label.
		/// </summary>
		/// <param name="g">The graphics.</param>
		private void PrintResourcesLabel(Graphics g) 
		{
			Rectangle rt = GetPrintRegionRectangle(Regions.ResourcesLabel);
			if (rt.Height<=0) return;

			using (Pen linePen = new Pen(ControlPaint.DarkDark(ResourcesLabelColor)))
			using (StringFormat sf = new StringFormat())
			using (Brush textBrush = new SolidBrush(ForeColor)) 
			{
				CalendarTools.FillRectangle(g, rt, ResourcesLabelColor, ResourcesLabelColorSecond, false);

				CalendarTools.SetAlignment(sf, ResourceAlign);

				for (int index = 0; index<Resources.Count; ++index) 
				{
					Rectangle rowRt = new Rectangle(0, index*resourceHeight+labelHeight, 
						ResourceWidth, resourceHeight-1);

					Resource resource = Resources[index];
					
					if (resource.BackColor!=Color.Empty)
						using (Brush catBrush = new SolidBrush(resource.BackColor))
							g.FillRectangle(catBrush, rowRt);

					g.DrawLine(linePen, rowRt.Left, rowRt.Bottom, rowRt.Right, rowRt.Bottom);

					rowRt.Inflate(-2, -2);
					g.DrawString(resource.Text, Font, textBrush, rowRt, sf);
				}
			}
		}
		#endregion

		#region - PrintWorkspace -
		/// <summary>
		/// Prints the workspace.
		/// </summary>
		/// <param name="g">The g.</param>
		private void PrintWorkspace(Graphics g) 
		{
			Rectangle rt = GetPrintRegionRectangle(Regions.Workspace);
			if (rt.Height<=0) return;
			GetDayInfoEventArgs gdi = null;

			using (Pen linePen = new Pen(LineColor))
			using (Pen lineDotPen = new Pen(LineColor))
			using (Brush selectedBrush = new SolidBrush(SelectedColor)) 
			{
				lineDotPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
				bool daysRes = HoursResolution==HoursResolutions.OneDay || HoursResolution==HoursResolutions.Twelve;
				int maxY = 0;

				if (rt.Width>0)
					CalendarTools.FillRectangle(g, rt, WorkHourColor, WorkHourColorSecond, true);

				/////////////////////////////////////////////////////////////////
				// Paint free days
				int cellsCount = cellItemCount;
				TimeSpan minRes = minsResolution;
				DateTime dt = PrintDate.Add(StartPrintTime);
				Rectangle colRt = new Rectangle(ResourceWidth, rt.Top, cellWidth, rt.Bottom);
				for (int i = 0; i<PrintCellItemCount; ++i) 
				{
					DateTime dt2 = dt.Date; 
					if (gdi==null || gdi.Date!=dt2) 
					{
						gdi = new GetDayInfoEventArgs(this, dt2);
						OnGetDayInfo(gdi);
					}

					bool isFree = gdi.FreeDay;
					bool isExtraBreak = false;
					if (!isFree && !daysRes) 
					{
						TimeSpan ts = dt.TimeOfDay;
						isFree = !(gdi.WorkHourBegin<=ts && ts<gdi.WorkHourEnd);
						TimeSpan t = ts + new TimeSpan(0,1,0,0);	
						isExtraBreak = gdi.Breaks.IsExtraBreak(ts, t);
						if(gdi.Breaks.IsBreak(ts, t)!=ts)
							isFree = true;
					}
					if(isExtraBreak)
					{
						using (Brush brush = new SolidBrush(isExtraBreak ? gdi.ExtraBreakHourColor : gdi.WorkHourColor))
							g.FillRectangle(brush, colRt);								
					}
					else if (isFree)
						using (Brush freeBrush = new SolidBrush(gdi.FreeHourColor))
							g.FillRectangle(freeBrush, colRt);
					else if (gdi.IndividualWorkColor)
						CalendarTools.FillRectangle(g, colRt, gdi.WorkHourColor, gdi.WorkHourColorSecond, true);
					//if(isExtraBreak) 
					//	using (Brush extBrush = new SolidBrush(gdi.ExtraBreakHourColor))
					//		g.FillRectangle(extBrush, colRt);

					if (gdi.resourcesInfo!=null) 
					{
						isExtraBreak = false;
						for (int index = 0; index<Resources.Count; ++index) 
						{
							GetDayInfoEventArgs.ResourceInfo ri = gdi.resourcesInfo[index];
							if (ri!=null) 
							{
								Rectangle rowRt = new Rectangle(0, index*resourceHeight+labelHeight, 
									ResourceWidth + PrintCellItemCount * CellWidth, resourceHeight-1);
								rowRt.Intersect(rt);
								rowRt.Intersect(colRt);

								bool isFreeRes = gdi.FreeDay;
								if (!isFreeRes && !daysRes) 
								{
									TimeSpan ts = dt.TimeOfDay;
									isFreeRes = !(ri.WorkHourBegin<=ts && ts<ri.WorkHourEnd);
									TimeSpan t = ts + new TimeSpan(0,1,0,0);								
									isExtraBreak = ri.Breaks.IsExtraBreak(ts, t);
									if(ri.Breaks.IsBreak(ts, t)!=ts)
										isFreeRes = true;
								}
								
								using (Brush brush = new SolidBrush(isFreeRes ? ri.FreeHourColor : ri.WorkHourColor))
									g.FillRectangle(brush, rowRt);	
								if(isExtraBreak)
								{
									using (Brush brush = new SolidBrush(isExtraBreak ? ri.ExtraBreakHourColor : ri.WorkHourColor))
										g.FillRectangle(brush, rowRt);								
								}
							}
						}
					}
				
					colRt.Offset(cellWidth, 0);
					dt += minRes;
				}

				/////////////////////////////////////////////////////////////////
				// Paint resource backgroud
				for (int index = 0; index<Resources.Count; ++index) 
				{
					Rectangle rowRt = new Rectangle(0, index*resourceHeight+labelHeight, 
						ResourceWidth, resourceHeight-1);
					maxY = rowRt.Bottom;

					Resource resource = Resources[index];

					if (resource.BackColor!=Color.Empty)
						using (Brush catBrush = new SolidBrush(resource.BackColor))
							g.FillRectangle(catBrush, rowRt);

					g.DrawLine(linePen, rowRt.Left, rowRt.Bottom, rowRt.Right, rowRt.Bottom);
				}

				////////////////////////////////////////////////////////////////
				// Paint net (cells)
				if (Resources.Count>0) 
				{
					bool ampm = CalendarTools.AMPM && !daysRes;
					dt = FirstDateTime;
					colRt = new Rectangle(ResourceWidth, rt.Top, cellWidth, maxY-rt.Top);
					bool first = true;
					for (int i = 0; i<PrintCellItemCount; ++i) 
					{
						bool boldLine = false;
						string lbl = null;
						getDateInfo(dt, ampm, ref first, ref boldLine, ref lbl);

						Pen pen = lbl!=null ? linePen : lineDotPen;
						g.DrawLine(pen, colRt.Left, colRt.Top, colRt.Left, colRt.Bottom);
						if (boldLine)
							g.DrawLine(pen, colRt.Left-1, colRt.Top, colRt.Left-1, colRt.Bottom);

						colRt.Offset(cellWidth, 0);
						dt += minRes;
					}
				}
			}

			currentApps = null;
			foreach (Appointment ap in CurrentApps)
			{
				InvokePrintAppointment(new PaintAppointmentEventArgs(g, ap));
			}
			currentApps = null;
		}
		#endregion

		#region - InvokePrintAppointment -
		/// <summary>
		/// Invokes the print appointment.
		/// </summary>
		/// <param name="args">The <see cref="ComponentGo.Calendars.PaintAppointmentEventArgs"/> instance containing the event data.</param>
		internal void InvokePrintAppointment(PaintAppointmentEventArgs args) 
		{
			OnPaintAppointment(args);
			if (!args.UserPaint)
				Print(args.Graphics, args.Appointment);
		}
		#endregion

		#region - Print -
		/// <summary>
		/// Prints the specified appointment.
		/// </summary>
		/// <param name="graphics">The graphics.</param>
		/// <param name="ap">The ap.</param>
		private void Print(Graphics graphics, Appointment ap) 
		{
			Rectangle rt = new Rectangle(0, ap.ResourceIndex*resourceHeight+labelHeight, 
				ResourceWidth + PrintCellItemCount * CellWidth, resourceHeight-1);

			rt.X = GetDateTimePrintLeftPoint(ap.dateStart);
			rt.Width = GetDateTimePrintLeftPoint(ap.dateStop)-rt.X;
			rt.Height -= 1;

			DrawAppointment(graphics, ap, rt);
		}

		private int GetDateTimePrintLeftPoint(DateTime dt) 
		{
			TimeSpan ts = dt-PrintDate.Add(StartPrintTime);
			int x = (int)(CalendarTools.Div(ts, minsResolution)*cellWidth);
			return ResourceWidth + x;
		}


		#endregion


		#endregion


		#region + PrintRectangles - 

		#region - PrintCanvasRectangle -
		/// <summary>
		/// Gets the rectangle for printing canvas.
		/// </summary>
		public override Rectangle PrintCanvasRectangle
		{
			get
			{
				if(printCanvasRectangle == Rectangle.Empty)
				{
					int width = 0;
					int height = 0;
					
					width = ResourceWidth + PrintCellItemCount * CellWidth;
					height = labelHeight + Resources.Count * resourceHeight;


					printCanvasRectangle = new Rectangle(0, 0, width, height);
				}
				return printCanvasRectangle;
			}
		}
		private Rectangle printCanvasRectangle = Rectangle.Empty;

		#endregion

		#region - GetPrintRegionRectangle -
		/// <summary>
		/// Returns a rectangle for a region for printing.
		/// </summary>
		/// <param name="region">Region, a method is to return a rectangle for.</param>
		/// <returns>Rectangle for a region. Coordinates relative to a control.</returns>
		public Rectangle GetPrintRegionRectangle(Regions region) 
		{
			switch (region) 
			{
				case Regions.DatesLabel:
					return new Rectangle(
						Point.Empty, 
						new Size(PrintCanvasRectangle.Width, labelHeight));

				case Regions.ResourcesLabel:
					return new Rectangle(
						new Point(0, labelHeight),
						new Size(ResourceWidth, Resources.Count * resourceHeight));

				case Regions.Workspace: 
				{
					Size size = new Size(PrintCellItemCount * CellWidth, Resources.Count * resourceHeight);
					return new Rectangle(
						new Point(ResourceWidth, labelHeight), size);
				}

			}
			throw new ArgumentException("Unexpected region value", "region");
		}
		#endregion


		#endregion

		#region + Print Times -
	
		#region - StartPrintTime - 
		/// <summary>
		/// Gets the start print time.
		/// </summary>
		/// <value>The start print time.</value>
		protected TimeSpan StartPrintTime
		{
			get {return startPrintTime;}
		}
		private TimeSpan startPrintTime = TimeSpan.FromHours(8);
		#endregion

		#region - FinishPrintTime - 
		/// <summary>
		/// Gets the finish print time.
		/// </summary>
		/// <value>The finish print time.</value>
		protected TimeSpan FinishPrintTime
		{
			get {return finishPrintTime;}
		}
		private TimeSpan finishPrintTime  = TimeSpan.FromHours(20);
		#endregion

		#region - PrintDate - 
		/// <summary>
		/// Gets the print date.
		/// </summary>
		/// <value>The print date.</value>
		public DateTime PrintDate
		{
			get
			{
				return FirstDateTime.Date;
			}
		}
		#endregion

		#endregion
		
		#region + Cells -

		#region - PrintCellItemCount -
		/// <summary>
		/// Gets the print cell item count.
		/// </summary>
		/// <value>The print cell item count.</value>
		protected int PrintCellItemCount
		{
			get
			{
				return (int)FinishPrintTime.Subtract(StartPrintTime).TotalMinutes / MinsInCell;
			}
		}
		#endregion

		#region - MinsInCell -

		/// <summary>
		/// Gets the mins in cell.
		/// </summary>
		/// <value>The mins in cell.</value>
		protected int MinsInCell
		{
			get
			{
				return (int)minsResolution.TotalMinutes;
			}
		}

		#endregion


		#endregion


		#endregion

		int cellItemCount 
		{
			get {
				int i = GetRegionRectangle(Regions.Workspace).Width;
				int result = i/cellWidth;
				if (i % cellWidth!=0) ++result;
				return result;
			}
		}

		/// <summary>
		/// Returns a rectangle containing area of a resource with a given index.
		/// </summary>
		/// <remarks>
		/// A given area contains regions <see cref="Regions.ResourcesLabel"/> and <see cref="Regions.Workspace"/>. 
		/// If a resource is not displayed on a control. a method returns a value of <see cref="Rectangle.Empty"/>.
		/// </remarks>
		/// <exception cref="ArgumentOutOfRangeException">
		/// Raised, when an index of a resource is less than 0 or if it is beyond the collection <see cref="ResourceCollection"/>.
		/// </exception>
		/// <param name="index">Index of the resource, an area is read for.</param>
		/// <returns>Rectangle covering whole area of a given resource.</returns>
		public Rectangle GetResourceRectangle(int index) {
			if (!(0<=index && index<Resources.Count))
				throw new ArgumentOutOfRangeException("index", index, "Argument out of range");

			if (index<FirstResource) return Rectangle.Empty;
			index -= FirstResource;
			
			int itemHeight = resourceHeight;
			int y = index*resourceHeight+labelHeight;
			if (y>=Height) return Rectangle.Empty;

			return new Rectangle(0, y, Width, itemHeight-1);
		}

		int resourceHeight {
			get {
				if (RowHeight==0)
					return (int)Font.GetHeight()+7;
				return RowHeight;
			}
		}

		/// <summary>
		/// Determines a number of resources visible in a calendar.
		/// </summary>
		/// <remarks>
		/// A number of resources displayed depends on height of a control and a font used.
		/// </remarks>
		[Browsable(false)]
		public int VisibleResources {
			get {
				int itemHeight = (int)Font.GetHeight()+7;
				int nr = GetRegionRectangle(Regions.ResourcesLabel).Height/itemHeight+1;
				return Math.Max(nr, 0);
			}
		}

		/// <summary>
		/// Determines a time period visible in a calendar.
		/// </summary>
		/// <remarks>
		/// A period depends on a <see cref="BaseCalendar.HoursResolution"/> property, 
		/// width of a calendar and size of a resource labels area <see cref="ResourceWidth"/>.
		/// </remarks>
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public TimeSpan VisibleTime {
			get {
				return CalendarTools.Mul(minsResolution, cellItemCount);
			}
		}

		/// <summary>
		/// Calculates a coordinate on a time axis of a left side of a given date/time.
		/// </summary>
		/// <param name="dt">Date and time a coordinate is calculated for.</param>
		/// <param name="truncated">Determines whether a coordinate should be rounded down to a size of a cell.</param>
		/// <returns>Left coordinate of a rectangle spanning cells containing a given time.</returns>
		public int GetDateTimeLeftPoint(DateTime dt, bool truncated) {
			TimeSpan ts = dt-FirstDateTime;
			int x;
			if (truncated)
				x = (int)CalendarTools.Div(ts, minsResolution)*cellWidth;
			else
				x = (int)(CalendarTools.Div(ts, minsResolution)*cellWidth);
			return ResourceWidth + x;
		}

		/// <summary>
		/// Calculates a non-rounded left coordinate on a time axis of a left side of a given date/time.
		/// </summary>
		/// <param name="dt">Date and time a coordinate is calculated for.</param>
		/// <remarks>A coordinate is not rounded. It is calculated to a pixel precision meaning it can be located inside a cell.</remarks>
		/// <returns>Left coordinate of a rectangle spanning cells containing a given time.</returns>
		public int GetDateTimeLeftPoint(DateTime dt) {
			return GetDateTimeLeftPoint(dt, false);
		}

		/// <summary>
		/// Returns a rectangle on a screen containing a given appointment.
		/// </summary>
		/// <param name="ap">Appointment, the rectangles are returned for.</param>
		/// <returns>A rectangle, calculated relative to 
		/// an upper left corner of the calendar control. 
		///	</returns>
		public Rectangle GetAppointmentRectangle(Appointment ap) {
			if (!(0<=ap.ResourceIndex && ap.ResourceIndex<Resources.Count))
				return Rectangle.Empty;
			Rectangle rt = GetResourceRectangle(ap.ResourceIndex);
			rt.X = GetDateTimeLeftPoint(ap.dateStart);
			rt.Width = GetDateTimeLeftPoint(ap.dateStop)-rt.X;
			rt.Height -= 1;

			return rt;
		}

		/// <summary>
		/// Calculates appointment's rectangle array, containing all the parts.
		/// </summary>
		/// <remarks>
		///	A table contains all rectangles contituting a displayed part of an appointment. 
		///	Parts that are not displayed (outside a currently displayed part of a calendar) are not placed in a table.<para/>
		///	This array can also contain <see cref="Rectangle.Empty"/> values.
		/// </remarks>
		/// <param name="ap">Appointment a rectangle is calculated for.</param>
		/// <returns>A calculated array containing rectangles.</returns>
		public override Rectangle[] GetAppointmentRectangles(Appointment ap) {
			return new Rectangle[] { GetAppointmentRectangle(ap) };
		}

		static int getTerminatorMargin(AppTerminator term) {
			switch (term) {
				case AppTerminator.Arrow:			
					return 4;
				case AppTerminator.TopArrow:
				case AppTerminator.BottomArrow:	
					return 6;
				case AppTerminator.Round:
					return 2;
			}
			return 0;
		}

		/// <summary>
		/// Calculates a rectangle, containing text located inside an appointment's rectangle.
		/// </summary>
		/// <remarks>
		///	Rectangle of appointment's text is also used as a rectangle of a text editor used to edit the text.
		/// </remarks>
		/// <param name="ap">Appointment a rectangle is calculated for.</param>
		/// <param name="eip">Determines whether a rectangle used by EiP <see cref="BaseCalendar.EditControl"/> should be calculated.</param>
		/// <returns>A rectangle calculated.</returns>
		public override Rectangle GetAppointmentTextRectangle(Appointment ap, bool eip) {
			Rectangle rt;
			if(printing)
			{
				rt = new Rectangle(0, ap.ResourceIndex*resourceHeight+labelHeight, 
					ResourceWidth + PrintCellItemCount * CellWidth, resourceHeight-1);

				rt.X = GetDateTimePrintLeftPoint(ap.dateStart);
				rt.Width = GetDateTimePrintLeftPoint(ap.dateStop)-rt.X;
				rt.Height -= 1;
			}
			else
			{
				rt = GetAppointmentRectangle(ap);
			}

			int clipLeft;
			int clipRight;
			if (eip) {
				clipLeft = ap.BeginTerminator==AppTerminator.Standard ? 0 : CellWidth/2;
				clipRight = ap.EndTerminator==AppTerminator.Standard ? 0 : CellWidth/2;
			}
			else {
				clipLeft = getTerminatorMargin(ap.BeginTerminator);
				clipRight = getTerminatorMargin(ap.EndTerminator);
			}

			if (rt.X<ResourceWidth) {
				int i = ResourceWidth-rt.X;
				rt.X += i;
				rt.Width -= i;
			}
			rt.X += clipLeft+1;
			rt.Width -= clipLeft+1;
			rt.Width -= clipRight;

			rt.Y += 1;
			rt.Height -= 1;
			
			return rt;
		}

		#endregion

		#region Hits

		/// <summary>
		/// Calculates a calendar region located at given coordinates.
		/// </summary>
		/// <param name="pt">Coordinates relative to a calendar control
		/// the region is calculated for.</param>
		/// <returns>Calculated region of a calendar control.</returns>
		public Regions HitTest(Point pt) {
			if (GetRegionRectangle(Regions.DatesLabel).Contains(pt)) return Regions.DatesLabel;
			if (GetRegionRectangle(Regions.ResourcesLabel).Contains(pt)) return Regions.ResourcesLabel;
			return Regions.Workspace;
		}

		/// <summary>
		/// Calculates a day and an hour located at given coordinates.
		/// </summary>
		/// <param name="pt">
		/// Coordinates relative to a calendar control, an datetime is calculated for.
		/// </param>
		/// <returns>Structure containing calculated information on day and hour.</returns>
		public override DateTime HitDateTimeTest(Point pt) {
			int x = pt.X-ResourceWidth;
			return FirstDateTime+CalendarTools.Mul(minsResolution, x/CellWidth);
		}

		/// <summary>
		/// Calculates an index of a resource located at a given point.
		/// </summary>
		/// <remarks>
		/// If a <b>pt</b> point is located in the first resource, the method returns -1.
		/// If a <b>pt</b> point is located below the last resource, the method returns <b>Resources.Count</b>.
		/// <seealso cref="BaseCollection.Count"/>
		/// </remarks>
		/// <param name="pt">Coordinates relative to a calendar control, 
		///		an resource is calculated for.</param>
		/// <returns>A calculated index of a resource.</returns>
		public override int HitResourceTest(Point pt) {
			int y = pt.Y-labelHeight;
			int idx = y/resourceHeight+FirstResource; 
			if (idx<0) return -1;
			if (idx>=Resources.Count) return Resources.Count;
			return idx;
		}

		/// <summary>
		/// Check which appointment object is located at given coordinates
		/// </summary>
		/// <param name="pt">Coordinates relative to a calendar control
		/// the appointment is searched for.</param>
		/// <returns>The object of an <see cref="Appointment"/> type located at given 
		/// coordinates or <b>null</b> in case there is no appointment at given coordinates.
		/// </returns>
		public override Appointment HitAppointmentTest(Point pt) {
			foreach (Appointment ap in CurrentApps)
				if (ap.Selected && GetAppointmentRectangle(ap).Contains(pt))
					return ap;
			foreach (Appointment ap in CurrentApps)
				if (!ap.Selected && GetAppointmentRectangle(ap).Contains(pt))
					return ap;

			return null;
		}

		internal override AppRegion HitAppointmentRegion(Appointment ap, Point pt) {
			Rectangle rt = GetAppointmentRectangle(ap);
			if (!rt.Contains(pt)) return AppRegion.None;
			if (pt.X<=rt.Left+3) return AppRegion.BeginMargin;
			if (pt.X>=rt.Right-3) return AppRegion.EndMargin;
			return AppRegion.Content;
		}

		#endregion

		#region Paint

		#region Labels

		void paintDatesLabel(Graphics g) {
			Rectangle rt = GetRegionRectangle(Regions.DatesLabel);
			rt.Height -= markHeight;
			if (rt.Width<=0) return;

			using (Font font = new Font("Arial", 9))
			using (Font fontHead = new Font("Arial", 11))
			using (Font fontAMPM = new Font("Arial", 7))
			using (Brush textBrush = new SolidBrush(ForeColor))
			using (Brush markBrush = new SolidBrush(SystemColors.Window))
			using (Pen markPen = new Pen(LineColor, 1))
			using (Pen linePen = new Pen(ControlPaint.Dark(DatesLabelColorSecond), 1)) {

				Font dateFont = DateFont==null ? fontHead : DateFont;

				CalendarTools.FillRectangle(g, rt, DatesLabelColor, DatesLabelColorSecond, true);
				bool daysRes = HoursResolution==HoursResolutions.OneDay || HoursResolution==HoursResolutions.Twelve;

				////////////////////////////////////////////////////////////////
				// Today marker
				//
				if (TodayMarkerVisible) {
					int xx = GetDateTimeLeftPoint(DateTime.Now, false);
					CalendarTools.FillRectangle(g, 
						new Rectangle(xx-10, rt.Bottom-20, 10, 20), 
						Color.FromArgb(0, TodayMarkerColor), TodayMarkerColor, false);
				}

				bool ampm = CalendarTools.AMPM && !daysRes;
				TimeSpan minRes = minsResolution;
				DateTime dt = FirstDateTime;
				int x = ResourceWidth;
				int firstDate = int.MaxValue;
				bool first = true;
				int cellsCount = cellItemCount;

				for (int i = 0; i<cellsCount; ++i) {

					string lbl = null;
					bool boldLine = false;

					if (getDateInfo(dt, ampm, ref first, ref boldLine, ref lbl))
						g.DrawString(dt.Hour<12 ? "am" : "pm", 
							fontAMPM, textBrush, x+14, rt.Height-23);

					int lineHeight = 8;
					if (lbl!=null) {
						g.DrawString(lbl, font, textBrush, x, rt.Height-22);
						lineHeight = 20;

						if (lbl!="" && (daysRes ? dt.Day==1 : dt.Hour==0 && dt.Minute==0 && dt.Second==0)) {
							g.DrawLine(linePen, x, 5, x, rt.Height-5);
							g.DrawString(dateToString(daysRes, dt), dateFont, textBrush, x, 5);
							if (firstDate==int.MaxValue)
								firstDate = x;
						}
					}
					g.DrawLine(linePen, x, rt.Bottom-lineHeight, x, rt.Bottom);
					if (boldLine)
						g.DrawLine(linePen, x-1, rt.Bottom-lineHeight, x-1, rt.Bottom);

					dt += minRes;
					x += cellWidth;
				}

				if (firstDate>ResourceWidth) {
					string s = dateToString(daysRes, FirstDateTime);
					float dateW = g.MeasureString(s, dateFont).Width;
					float xx = firstDate-dateW;
					if (xx>ResourceWidth) xx = ResourceWidth;
					g.DrawString(s, dateFont, textBrush, xx, 5);
				}

				//////////////////////////////////////////////////////////////////
				// Selection ruler
				//
				rt.Y = rt.Bottom;
				rt.Height = markHeight;
				g.FillRectangle(markBrush, rt);
				foreach (Appointment ap in CurrentApps) {
					if (ap.Selected) {
						int x1 = GetDateTimeLeftPoint(ap.dateStart, false);
						int x2 = GetDateTimeLeftPoint(ap.dateStop, false);
						using (Brush busyBrush = new SolidBrush(ap.BusyColor==Color.Empty ? SelectedColor : ap.BusyColor))
							g.FillRectangle(busyBrush, x1, rt.Top, x2-x1+1, rt.Height);
					}
				}
				if (SelectedDateBegin<SelectedDateEnd) {
					int x1 = GetDateTimeLeftPoint(SelectedDateBegin, false);
					int x2 = GetDateTimeLeftPoint(SelectedDateEnd, false);
					using (Brush busyBrush = new SolidBrush(SelectedColor))
						g.FillRectangle(busyBrush, x1, rt.Top, x2-x1+1, rt.Height);
				}
				g.DrawLine(markPen, rt.Left, rt.Top, rt.Right, rt.Top);
				g.DrawLine(markPen, rt.Left, rt.Bottom-1, rt.Right, rt.Bottom-1);
			}
		}

		bool getDateInfo(DateTime dt, bool ampm, ref bool first, ref bool boldLine, ref string lbl) {
			int hour = dt.Hour;
			if (ampm) {
				if (hour==0) hour = 12;
				else if (hour>12) hour -= 12;
			}
			switch (HoursResolution) {
				case HoursResolutions.OneDay:
					if (dt.Day % 2==1 && dt.AddDays(1).Month==dt.Month)
						lbl = dt.Day.ToString();
					boldLine = dt.DayOfWeek==DayOfWeek.Monday;
					break;

				case HoursResolutions.Twelve:
					if (dt.TimeOfDay==TimeSpan.Zero) {
						lbl = dt.Day.ToString();
						boldLine = dt.DayOfWeek==DayOfWeek.Monday;
					}
					break;

				case HoursResolutions.One:
					if (hour % 2 == 0)
						lbl = hour.ToString();
					boldLine = hour % 12 == 0 && dt.Minute==0;
					break;

				case HoursResolutions.Half:
					if (dt.Minute==0)
						lbl = hour.ToString();
					boldLine = hour % 3 == 0 && dt.Minute==0;
					break;

				case HoursResolutions.Quart:
				case HoursResolutions.TenMinutes:
				case HoursResolutions.SixMinutes:
				case HoursResolutions.FiveMinutes:
					if (dt.Minute==0) {
						lbl = hour.ToString();
						boldLine = true;
					}
					else if (dt.Minute==30)
						lbl = "";
					break;

				case HoursResolutions.Minute:
					if (dt.Minute % 15==0) {
						lbl = dt.ToString("hh:mm");
						boldLine = true;
					}
					else if (dt.Minute % 5==0)
						lbl = "";
					break;

				case HoursResolutions.QuarterMinute:
					if (dt.Minute % 5==0 && dt.Second==0) {
						lbl = dt.ToString("hh:mm");
						boldLine = true;
					}
					else if (dt.Second==0)
						lbl = "";
					break;

				case HoursResolutions.Second:
					if (dt.Second % 15==0) {
						lbl = dt.ToString("hh:mm:ss");
						boldLine = true;
					}
					else if (dt.Second % 5==0)
						lbl = "";
					break;
			}
			if (ampm && dt.Minute==0) {
				if (hour==12) {
					first = false;
					lbl = "12";
					return true;
				}
				if (first) {
					first = false;
					return true;
				}
			}
			return false;
		}

		string dateToString(bool daysRes, DateTime dt) {
			if (DateFormat!=null)
				return dt.ToString(DateFormat);
			return daysRes ? dt.ToString("y") : dt.ToString("D");
		}

		void paintResourcesLabel(Graphics g) {
			Rectangle rt = GetRegionRectangle(Regions.ResourcesLabel);
			if (rt.Height<=0) return;

			using (Pen linePen = new Pen(ControlPaint.DarkDark(ResourcesLabelColor)))
			using (StringFormat sf = new StringFormat())
			using (Brush textBrush = new SolidBrush(ForeColor)) {
				CalendarTools.FillRectangle(g, rt, ResourcesLabelColor, ResourcesLabelColorSecond, false);

				CalendarTools.SetAlignment(sf, ResourceAlign);

				for (int index = FirstResource; index<Resources.Count; ++index) {
					Rectangle rowRt = GetResourceRectangle(index);
					rowRt.Intersect(rt);
					if (rowRt.IsEmpty) break;

					Resource resource = Resources[index];
					
					if (resource.BackColor!=Color.Empty)
						using (Brush catBrush = new SolidBrush(resource.BackColor))
							g.FillRectangle(catBrush, rowRt);

					g.DrawLine(linePen, rowRt.Left, rowRt.Bottom, rowRt.Right, rowRt.Bottom);

					rowRt.Inflate(-2, -2);
					g.DrawString(resource.Text, Font, textBrush, rowRt, sf);
				}
			}
		}

		#endregion

		#region Workspace

		void paintWorkspace(Graphics g) {
			Rectangle rt = GetRegionRectangle(Regions.Workspace);
			if (rt.Height<=0) return;
			GetDayInfoEventArgs gdi = null;

			using (Pen linePen = new Pen(LineColor))
			using (Pen lineDotPen = new Pen(LineColor))
			using (Brush selectedBrush = new SolidBrush(SelectedColor)) {
				lineDotPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
				bool daysRes = HoursResolution==HoursResolutions.OneDay || HoursResolution==HoursResolutions.Twelve;
				int maxY = 0;

				if (rt.Width>0)
					CalendarTools.FillRectangle(g, rt, WorkHourColor, WorkHourColorSecond, true);

				/////////////////////////////////////////////////////////////////
				// Paint free days
				int cellsCount = cellItemCount;
				TimeSpan minRes = minsResolution;
				DateTime dt = FirstDateTime;
				Rectangle colRt = new Rectangle(ResourceWidth, rt.Top, cellWidth, rt.Bottom);
				for (int i = 0; i<cellsCount; ++i) {
					DateTime dt2 = dt.Date; 
					if (gdi==null || gdi.Date!=dt2) {
						gdi = new GetDayInfoEventArgs(this, dt2);
						OnGetDayInfo(gdi);
					}

					bool isFree = gdi.FreeDay;
					bool isExtraBreak = false;
					if (!isFree && !daysRes) 
					{
						TimeSpan ts = dt.TimeOfDay;
						isFree = !(gdi.WorkHourBegin<=ts && ts<gdi.WorkHourEnd);
						TimeSpan t = ts + new TimeSpan(0,1,0,0);	
						isExtraBreak = gdi.Breaks.IsExtraBreak(ts, t);
						if(gdi.Breaks.IsBreak(ts, t)!=ts)
							isFree = true;
					}
					if(isExtraBreak)
					{
						using (Brush brush = new SolidBrush(isExtraBreak ? gdi.ExtraBreakHourColor : gdi.WorkHourColor))
							g.FillRectangle(brush, colRt);								
					}
					else if (isFree)
						using (Brush freeBrush = new SolidBrush(gdi.FreeHourColor))
							g.FillRectangle(freeBrush, colRt);
					else if (gdi.IndividualWorkColor)
						CalendarTools.FillRectangle(g, colRt, gdi.WorkHourColor, gdi.WorkHourColorSecond, true);
					//if(isExtraBreak) 
					//	using (Brush extBrush = new SolidBrush(gdi.ExtraBreakHourColor))
					//		g.FillRectangle(extBrush, colRt);

					if (gdi.resourcesInfo!=null) {
						isExtraBreak = false;
						for (int index = FirstResource; index<Resources.Count; ++index) {
							GetDayInfoEventArgs.ResourceInfo ri = gdi.resourcesInfo[index];
							if (ri!=null) {
								Rectangle rowRt = GetResourceRectangle(index);
								rowRt.Intersect(rt);
								if (rowRt.IsEmpty) break;
								rowRt.Intersect(colRt);

								bool isFreeRes = gdi.FreeDay;
								if (!isFreeRes && !daysRes) 
								{
									TimeSpan ts = dt.TimeOfDay;
									isFreeRes = !(ri.WorkHourBegin<=ts && ts<ri.WorkHourEnd);
									TimeSpan t = ts + new TimeSpan(0,1,0,0);								
									isExtraBreak = ri.Breaks.IsExtraBreak(ts, t);
									if(ri.Breaks.IsBreak(ts, t)!=ts)
										isFreeRes = true;
								}
								
								using (Brush brush = new SolidBrush(isFreeRes ? ri.FreeHourColor : ri.WorkHourColor))
									g.FillRectangle(brush, rowRt);	
								if(isExtraBreak)
								{
									using (Brush brush = new SolidBrush(isExtraBreak ? ri.ExtraBreakHourColor : ri.WorkHourColor))
											   g.FillRectangle(brush, rowRt);								
								}
							}
						}
					}
				
					colRt.Offset(cellWidth, 0);
					dt += minRes;
				}

				/////////////////////////////////////////////////////////////////
				// Paint resource backgroud
				for (int index = FirstResource; index<Resources.Count; ++index) {
					Rectangle rowRt = GetResourceRectangle(index);
					rowRt.Intersect(rt);
					if (rowRt.IsEmpty) break;
					maxY = rowRt.Bottom;

					Resource resource = Resources[index];

					if (resource.BackColor!=Color.Empty)
						using (Brush catBrush = new SolidBrush(resource.BackColor))
							g.FillRectangle(catBrush, rowRt);

					g.DrawLine(linePen, rowRt.Left, rowRt.Bottom, rowRt.Right, rowRt.Bottom);
				}

				////////////////////////////////////////////////////////////////
				// Paint selection
				if (0<=SelectedResource && SelectedResource<Resources.Count) {
					Rectangle selRt = GetResourceRectangle(SelectedResource);
					selRt.X = GetDateTimeLeftPoint(SelectedDateBegin);
					selRt.Width = GetDateTimeLeftPoint(SelectedDateEnd)-selRt.X;
					selRt.Intersect(rt);
					g.FillRectangle(selectedBrush, selRt);
				}

				////////////////////////////////////////////////////////////////
				// Paint net (cells)
				if (Resources.Count>0) {
					bool ampm = CalendarTools.AMPM && !daysRes;
					dt = FirstDateTime;
					colRt = new Rectangle(ResourceWidth, rt.Top, cellWidth, maxY-rt.Top);
					bool first = true;
					for (int i = 0; i<cellsCount; ++i) {
						bool boldLine = false;
						string lbl = null;
						getDateInfo(dt, ampm, ref first, ref boldLine, ref lbl);

						Pen pen = lbl!=null ? linePen : lineDotPen;
						g.DrawLine(pen, colRt.Left, colRt.Top, colRt.Left, colRt.Bottom);
						if (boldLine)
							g.DrawLine(pen, colRt.Left-1, colRt.Top, colRt.Left-1, colRt.Bottom);

						colRt.Offset(cellWidth, 0);
						dt += minRes;
					}
				}
			}

			g.SetClip(rt);
			foreach (Appointment ap in CurrentApps)
				if (!ap.Selected)
					InvokePaintAppointment(new PaintAppointmentEventArgs(g, ap));
			foreach (Appointment ap in CurrentApps)
				if (ap.Selected)
					InvokePaintAppointment(new PaintAppointmentEventArgs(g, ap));
			paintLinks(g);
			g.ResetClip();
		}

		int getYTerminator(AppTerminator term, Rectangle rt, bool top) {
			switch (term) {
				case AppTerminator.Round:
				case AppTerminator.Arrow:			
					return rt.Top+rt.Height/2;
				case AppTerminator.TopArrow:
					return rt.Top;
				case AppTerminator.BottomArrow:	
					return rt.Bottom;
			}
			return top ? rt.Top : rt.Bottom;
		}

		void paintLinks(Graphics g) {
			using (Pen pen = new Pen(ControlPaint.Dark(LineColor), 2)) {
				pen.DashStyle = DashStyle.Dash;
				for (int i = 0; i<Appointments.Count-1; ++i) {
					Appointment ap1 = Appointments[i];
					Appointment ap2 = Appointments[i+1];
					if (ap1.LinkWithNext && ap1.dateStop==ap2.dateStart) {
						Rectangle rt1 = GetAppointmentRectangle(ap1);
						Rectangle rt2 = GetAppointmentRectangle(ap2);
						int y1, y2;
						if (ap1.ResourceIndex==ap2.ResourceIndex) {
							y1 = rt1.Top; y2 = rt2.Bottom;
						}
						else {
							bool top = ap1.ResourceIndex<=ap2.ResourceIndex;
							y1 = getYTerminator(ap1.EndTerminator, rt1, top);
							y2 = getYTerminator(ap2.BeginTerminator, rt2, !top);
						}
						g.DrawLine(pen, rt1.Right, y1, rt1.Right, y2+1);
					}
				}
			}
		}
			
		#endregion

		#region Appointment

		/// <summary>
		/// Draws an appointment in a selected graphics object.
		/// </summary>
		/// <param name="graphics">Graphics object used to display an appointment.</param>
		/// <param name="ap">An appointment drawn in a calendar. </param>
		/// 
		public override void Paint(Graphics graphics, Appointment ap) 
		{
			DrawAppointment(graphics, ap, GetAppointmentRectangle(ap));
		}

		/// <summary>
		/// Draws the appointment.
		/// </summary>
		/// <param name="graphics">The graphics.</param>
		/// <param name="ap">The ap.</param>
		/// <param name="rt">The rt.</param>
		private void DrawAppointment(Graphics graphics, Appointment ap, Rectangle rt) 
		{
			if (rt.Height<=0) return;

			Color backColor = ap.Selected ? SelectedAppointmentColor : ap.GetBackColor();
			Color progressColor = ap.ProgressColor;
			Color lineColor = ap.GetLineColor();
			Color textColor = ap.Selected ? SelectedAppointmentTextColor  : ap.GetForeColor();
			if (ap.IsGhost) 
			{
				backColor = Color.FromArgb(102, backColor);
				lineColor = Color.FromArgb(102, lineColor);
				textColor = Color.FromArgb(102, textColor);
				progressColor = Color.FromArgb(102, progressColor);
			}

			using (GraphicsPath path = new GraphicsPath())
			using (StringFormat textFormat = new StringFormat(StringFormatFlags.DisplayFormatControl))
			using (Brush textBrush = new SolidBrush(textColor))
			using (Brush progressBrush = new SolidBrush(progressColor))
			using (Pen linePen = new Pen(lineColor))
			using (Brush backBrush = new SolidBrush(backColor)) 
			{

				int l = rt.Height/2;
				Point [] pts = null;

				switch (ap.BeginTerminator) 
				{
					case AppTerminator.Standard:
						pts = new Point[] {
											  new Point(rt.Left+l, rt.Bottom-1), 
											  new Point(rt.Left, rt.Bottom-1), 
											  new Point(rt.Left, rt.Top+1), 
											  new Point(rt.Left+l, rt.Top+1)};
						break;
					case AppTerminator.Arrow:
						pts = new Point[] {
											  new Point(rt.Left+l, rt.Bottom), 
											  new Point(rt.Left, rt.Top+l), 
											  new Point(rt.Left+l, rt.Top)};
						break;
					case AppTerminator.TopArrow:
						pts = new Point[] {
											  new Point(rt.Left+l, rt.Bottom), 
											  new Point(rt.Left, rt.Top), 
											  new Point(rt.Left+l, rt.Top)};
						break;
					case AppTerminator.BottomArrow:
						pts = new Point[] {
											  new Point(rt.Left+l, rt.Bottom), 
											  new Point(rt.Left, rt.Bottom), 
											  new Point(rt.Left+l, rt.Top)};
						break;
					case AppTerminator.Round:
						path.AddArc(rt.Left, rt.Top, rt.Height, rt.Height, 90, 180);
						break;
				}

				if (pts!=null)
					path.AddLines(pts);
				path.AddLine(rt.Left+l, rt.Top+1, rt.Right-l, rt.Top+1);

				pts = null;
				switch (ap.EndTerminator) 
				{
					case AppTerminator.Standard:
						pts = new Point[] {
											  new Point(rt.Right-l, rt.Top+1), 
											  new Point(rt.Right, rt.Top+1), 
											  new Point(rt.Right, rt.Bottom-1), 
											  new Point(rt.Right-l, rt.Bottom-1)};
						break;
					case AppTerminator.Arrow:
						pts = new Point[] {
											  new Point(rt.Right-l, rt.Top), 
											  new Point(rt.Right, rt.Top+l), 
											  new Point(rt.Right-l, rt.Bottom)};
						break;
					case AppTerminator.TopArrow:
						pts = new Point[] {
											  new Point(rt.Right-l, rt.Top), 
											  new Point(rt.Right, rt.Top), 
											  new Point(rt.Right-l, rt.Bottom)};
						break;
					case AppTerminator.BottomArrow:
						pts = new Point[] {
											  new Point(rt.Right-l, rt.Top), 
											  new Point(rt.Right, rt.Bottom), 
											  new Point(rt.Right-l, rt.Bottom)};
						break;
					case AppTerminator.Round:
						path.AddArc(rt.Right-rt.Height, rt.Top, rt.Height, rt.Height, 270, 180);
						break;
				}

				if (pts!=null)
					path.AddLines(pts);
				path.AddLine(rt.Right-l, rt.Bottom-1, rt.Left+l, rt.Bottom-1);

				graphics.FillPath(backBrush, path);
				graphics.DrawPath(linePen, path);

				Rectangle textRt = GetAppointmentTextRectangle(ap, false);
				//////////////////////////////////////////////////////
				// Progress bar
				//
				if (ap.Progress>0) 
				{
					Rectangle progressRt = textRt;
					progressRt.Inflate(-2, -2);
					progressRt.Width = progressRt.Width*ap.Progress/100;
					graphics.FillRectangle(progressBrush, progressRt);
				}
				
				//////////////////////////////////////////////////////
				// Text
				// 	
				CalendarTools.SetAlignment(textFormat, TextAlign);
				textFormat.Trimming = StringTrimming.EllipsisCharacter;
				textFormat.FormatFlags = StringFormatFlags.LineLimit; 
				
				int charFitted; 
				int linesFitted; 
				graphics.MeasureString(ap.Text, ap.Font==null ? Font : ap.Font, textRt.Size, textFormat, out charFitted, out linesFitted);     
				graphics.DrawString(ap.Text.Substring(0, charFitted) + ((charFitted < ap.Text.Length)?"...":""), 
					ap.Font==null ? Font : ap.Font, 
					textBrush, textRt, textFormat);
			}
		}

		#endregion

		/// <summary>
		/// Overloaded. Implementation of a calendar.
		/// </summary>
		/// <param name="pe">Like overloaded</param>
		protected override void OnPaint(PaintEventArgs pe) {
			Graphics g = pe.Graphics;

			paintDatesLabel(g);
			paintResourcesLabel(g);
			paintWorkspace(g);
			paintScrollbarSpace(g);

			base.OnPaint(pe);
		}

		#endregion

		#region Printing


		#endregion

		#region Mouse

		internal override void mouseClick(System.Windows.Forms.MouseEventArgs e, bool grow) 
		{
			Point pt = new Point(e.X, e.Y);

			/////////////////////////////////////////////////////////////
			// Select hours
			// 
			Regions region = HitTest(pt);
			if (region==Regions.ResourcesLabel) {
				int resourceIdx = HitResourceTest(pt);
				if (0<=resourceIdx && resourceIdx<Resources.Count-1)
					SelectedResource = resourceIdx;
			}
			else {
				DateTime dt = HitDateTimeTest(pt);
				if (dt==DateTime.MinValue) return;
				int resourceIdx = region==Regions.Workspace ? HitResourceTest(pt) : -1;
				if (resourceIdx<0) resourceIdx = SelectedResource;

				Appointments.UnselectAll();
				moveCursor(grow, dt, minsResolution, resourceIdx);
			}
		}

		internal override Cursor getCursorAppearance(Point pt, Appointment ap) {
			return Cursors.SizeWE;
		}

		internal override bool recalcAllDayOnDrag(Point pt, Appointment ap) { 
			return false; 
		}
	
		#endregion

		#region Keyboard

		internal override void checkSelected() {
			if (SelectedDateBegin==DateTime.MinValue) {
				SelectedDateBegin = FirstDateTime;
				SelectedDateEnd = SelectedDateBegin+minsResolution;
			}
		}

		internal override bool processKey(KeyEventArgs e) {
			TimeSpan cursorTime = minsResolution;
			switch (e.KeyData) {
				case Keys.Down:					
				case Keys.Shift | Keys.Down:
					if (SelectedResource<Resources.Count-1)
						++SelectedResource;
					return true;
				case Keys.Up:					
				case Keys.Shift | Keys.Up:
					if (SelectedResource>0)
						--SelectedResource;
					return true;

				case Keys.PageUp:
				case Keys.Shift | Keys.PageUp: {
					FirstResource = Math.Max(FirstResource-VisibleResources, 0);
					SelectedResource = Math.Max(SelectedResource-VisibleResources, 0);
					break;
				}

				case Keys.PageDown:
				case Keys.Shift | Keys.PageDown: {
					if (Resources.Count-VisibleResources+1>=0) 
					{
						FirstResource = Math.Min(FirstResource+VisibleResources, Resources.Count-VisibleResources+1);
						SelectedResource = Math.Min(SelectedResource+VisibleResources, Resources.Count-1);
					}
					break;
				}

				case Keys.Right:				
				case Keys.Shift | Keys.Right:				
					moveCursor(e.Shift, cursorTime, cursorTime, SelectedResource); 
					return true;
				case Keys.Left:					
				case Keys.Shift | Keys.Left:					
					moveCursor(e.Shift, -cursorTime, cursorTime, SelectedResource); 
					return true;
				case Keys.Home:					
				case Keys.Shift | Keys.Home:					
					moveCursor(e.Shift, FirstDateTime, cursorTime, SelectedResource); 
					return true;
				case Keys.End:					
				case Keys.Shift | Keys.End:					
					moveCursor(e.Shift, FirstDateTime+VisibleTime-cursorTime, cursorTime, SelectedResource); 
					return true;
				case Keys.Control | Keys.Home:					
				case Keys.Control | Keys.Shift | Keys.Home:
					moveCursor(e.Shift, 
						new DateTime(FirstDateTime.Year, FirstDateTime.Month, 1), 
						cursorTime, SelectedResource); 
					return true;
				case Keys.Control | Keys.End:					
				case Keys.Control | Keys.Shift | Keys.End:					
					moveCursor(e.Shift, 
						new DateTime(FirstDateTime.Year, FirstDateTime.Month, 1).AddMonths(1)-cursorTime, 
						cursorTime, SelectedResource); 
					return true;
			}
			return false;
		}

		internal override Appointment getNext(Appointment ap) {
			int i = CurrentApps.IndexOf(ap)+1;
			if (i<CurrentApps.Count)
				return (Appointment)CurrentApps[i];
			return null;
		}

		internal override Appointment getPrevious(Appointment ap) {
			if (ap==null) {
				if (CurrentApps.Count>0)
					return (Appointment)CurrentApps[CurrentApps.Count-1];
			}
			else {
				int i = CurrentApps.IndexOf(ap);
				if (i>0) 
					return (Appointment)CurrentApps[i-1];
			}
			return null;
		}

		#endregion

	}

#endif
}
